#pragma once

#include "Object.h"
#include "SQLObject.h"
#include "SQLExpr.h"

#include <string>
#include <sstream>
#include <map>
#include <list>
#include "../../Exception/UnsupportedOperationException.h"

class SQLObjectImpl
    : virtual public SQLObject
{
public:
  SQLObject_ptr parent;
  std::shared_ptr<std::map<std::string, Object_ptr>> attributes;

  typedef SQLObject super;
  std::stringstream buf;
  SQLCommentHint_ptr hint;

public:
  SHAREDOBJECT(SQLObjectImpl);
  SQLObjectImpl();
  virtual ~SQLObjectImpl();
  virtual void accept(SQLASTVisitor_ptr visitor) override;
  virtual void output(std::stringstream *buf);
  virtual string_ptr toString() override;
  virtual SQLObject_ptr getParent() override;
  void setParent(SQLObject_ptr parent);
  Object_ptr getAttribute(std::string name)
  {
    if (attributes == nullptr)
    {
      return nullptr;
    }

    auto it = attributes->find(name);
    if (it == attributes->end())
    {
      return nullptr;
    }
    return it->second;
  }
  void putAttribute(std::string name, Object_ptr value)
  {
    if (attributes == nullptr)
    {
      attributes = std::make_shared<std::map<std::string, Object_ptr>>();
    }

    attributes->insert(std::make_pair(name, value));
  }
  std::shared_ptr<std::map<std::string, Object_ptr>> getAttributes();

  virtual std::shared_ptr<std::map<std::string, Object_ptr>> getAttributesDirect();

  virtual string_ptr getClass();

  void setSourceLine(int sourceLine) { this->sourceLine = sourceLine; }
  void setSourceColumn(int sourceColumn) { this->sourceColumn = sourceColumn; }

  virtual void accept0(SQLASTVisitor_ptr visitor){};
  void acceptChild(SQLASTVisitor_ptr visitor, SQLObject_list_ptr children);
  void acceptChild(SQLASTVisitor_ptr visitor, SQLObject_ptr child);
  SQLCommentHint_ptr getHint();
  void setHint(SQLCommentHint_ptr hint);

  // @SuppressWarnings("unchecked")
  void addBeforeComment(string_ptr comment)
  {
    if (comment == nullptr)
    {
      return;
    }

    if (attributes == nullptr)
    {
      attributes = std::make_shared<std::map<std::string, Object_ptr>>();
    }

    string_list_ptr comments = nullptr;
    auto tmp = attributes->find("rowFormat.before_comment");
    if (tmp != attributes->end())
    {
      comments = tmp->second->getLS();
    }
    if (comments == nullptr)
    {
      comments = std::make_shared<std::list<string_ptr>>();
      attributes->insert(std::make_pair("rowFormat.before_comment", Object_ptr(new Object(comments))));
    }

    comments->push_back(comment);
  }

  // @SuppressWarnings("unchecked")
  void addBeforeComment(string_list_ptr comments)
  {
    if (attributes == nullptr)
    {
      attributes = std::make_shared<std::map<std::string, Object_ptr>>();
    }

    string_list_ptr attrComments = nullptr;
    auto tmp = attributes->find("rowFormat.before_comment");
    if (tmp != attributes->end())
    {
      attrComments = tmp->second->getLS();
    }

    if (attrComments == nullptr)
    {
      attributes->insert(std::make_pair("rowFormat.before_comment", Object_ptr(new Object(comments))));
    }
    else
    {
      // attrComments.addAll(comments);
      for (auto it = comments->begin(); it != comments->end(); it++)
      {
        {
          attrComments->push_back(*it);
        }
      }
    }
  }

  bool containsAttribute(string_ptr name)
  {
    if (attributes == nullptr)
    {
      return false;
    }

    return attributes->count(name->c_str()) > 0;
  }

  string_list_ptr getBeforeCommentsDirect()
  {
    if (attributes == nullptr)
    {
      return nullptr;
    }

    auto it = attributes->find("rowFormat.before_comment");
    if (it != attributes->end())
    {
      return it->second->getLS();
    }
    else
    {
      return nullptr;
    }
  }

  void addAfterComment(string_ptr comment)
  {
    if (attributes == nullptr)
    {
      attributes = std::make_shared<std::map<std::string, Object_ptr>>();
    }

    string_list_ptr comments = nullptr;
    auto it = attributes->find("rowFormat.after_comment");
    if (it != attributes->end())
    {
      comments = it->second->getLS();
    }
    if (comments == nullptr)
    {
      comments = std::make_shared<std::list<string_ptr>>();
      attributes->insert(std::make_pair("rowFormat.after_comment", Object_ptr(new Object(comments))));
    }

    comments->push_back(comment);
  }

  void addAfterComment(string_list_ptr comments)
  {
    if (comments == nullptr)
    {
      return;
    }

    if (attributes == nullptr)
    {
      attributes = std::make_shared<std::map<std::string, Object_ptr>>();
    }

    string_list_ptr attrComments = nullptr;
    auto it = attributes->find("rowFormat.after_comment");
    if (it != attributes->end())
    {
      attrComments = it->second->getLS();
    }
    if (attrComments == nullptr)
    {
      attributes->insert(std::make_pair("rowFormat.after_comment", Object_ptr(new Object(comments))));
    }
    else
    {
      // attrComments.addAll(comments);
      for (auto it = comments->begin(); it != comments->end(); it++)
      {
        {
          attrComments->push_back(*it);
        }
      }
    }
  }

  string_list_ptr getAfterCommentsDirect()
  {
    if (attributes == nullptr)
    {
      return nullptr;
    }

    auto it = attributes->find("rowFormat.after_comment");
    if (it == attributes->end())
    {
      return nullptr;
    }
    return it->second->getLS();
  }

  bool hasBeforeComment()
  {
    if (attributes == nullptr)
    {
      return false;
    }

    string_list_ptr comments = nullptr;
    auto it = attributes->find("rowFormat.before_comment");
    if (it != attributes->end())
    {
      comments = it->second->getLS();
    }

    if (comments == nullptr)
    {
      return false;
    }

    return !comments->empty();
  }

  bool hasAfterComment()
  {
    if (attributes == nullptr)
    {
      return false;
    }

    string_list_ptr comments = nullptr;
    auto it = attributes->find("rowFormat.after_comment");
    if (it != attributes->end())
    {
      comments = it->second->getLS();
    }
    if (comments == nullptr)
    {
      return false;
    }

    return !comments->empty();
  }

  virtual SQLObject_ptr clone()
  {
    throw new UnsupportedOperationException(this->getClass());
  }
protected:
  int sourceLine;
  int sourceColumn;
};
